package com.mazaiting.util;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * 无符号数工具类
 * java语言数字工具类，支持把其他语言的无符号的数字转换为java语言的数字类型，并且不会改变正负号。
 *
 * java语言的基础数据类型都是有符号，当从其他语言接收到一个无符号的数字时，java语言会使用具有相同字节数的数字类型来接收，并可能改变其符号：
 * 比如： c语言的unsigned int32 值 0xffffffff， java语言的int类型接收后会识别为负数，而这并不是业务上所期望的，因此对于这类数据
 * 需要使用字节数多的数据类型来接收，比如上面的例子需要使用long数据类型来接收，如果是 c语言的unsigned int64值需要使用java的BigInteger来接收。
 * @author mazaiting
 * @since 2021/8/13
 */
public class UnsignedNumberUtil {

    /**
     * 判断一个byte类型的数字是否为负数，如果为负数需要使用更宽类型的数据类型来接收，比如short，int，long等，一般使用short接收。
     *
     * @param value 值
     * @return true: 需要; false: 不需要
     */
    public static boolean needWiden(byte value){
        // 0x80 = 10000000,即2的7次方，对于byte型数字来说就是符号位，如果符号位为1，则该数字为负数
        // 0x80也就是Byte.MIN_VALUE
        return (value & 0x80) == 0x80;
    }
    /**
     * 判断一个short类型的数字是否为负数，如果为负数需要使用更宽类型的数据类型来接收，比如int，long等，一般使用int接收。
     *
     * @param value 值
     * @return true: 需要; false: 不需要
     */
    public static boolean needWiden(short value){
        // 0x80000，即2的15次方，对于int型数字来说就是符号位，如果符号位为1，则该数字为负数
        // 0x8000也就是Short.MIN_VALUE
        return (value & 0x8000) == 0x8000;
    }
    /**
     * 判断一个int类型的数字是否为负数，如果为负数需要使用更宽类型的数据类型来接收，使用long接收。
     *
     * @param value 值
     * @return true: 需要; false: 不需要
     */
    public static boolean needWiden(int value){
        // 0x80000000，即2的31次方，对于int型数字来说就是符号位，如果符号位为1，则该数字为负数
        // 0x80000000也就是Integer.MIN_VALUE
        return (value & 0x80000000) == 0x80000000;
    }
    /**
     * 判断一个long类型的数字是否为负数，如果为负数需要BigInteger，这是因为java基础类型中字节数最多的就是long了，超过long表示范围的需要使用java库提供的
     * BigInteger 对象类型
     *
     * @param value 值
     * @return true: 需要; false: 不需要
     */
    public static boolean needWiden(long value){
        // 0x8000000000000000L，即2的63次方，对于int型数字来说就是符号位，如果符号位为1，则该数字为负数
        // 0x8000000000000000L也就是Long.MIN_VALUE
        // 注意后面那个L，如果没有这个L，java会认为这个数字为int类型
        return (value & 0x8000000000000000L) == 0x8000000000000000L;
    }
    /**
     * byte类型转换为short类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
     * @param value 有符号 byte 值
     * @return 无符号byte值
     */
    public static short toUnsignedByteValue(byte value){
        if(!needWiden(value)){
            return value;
        }
        // 使用 与 运算把高为字节设置为0，符号位为0，该数字为正数
        return (short)((short) value & 0xff);
    }
    /**
     * short类型转换为int类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
     * @param value 有short值
     * @return 无符号 short 值
     */
    public static int toUnsignedShortValue(short value){
        if(!needWiden(value)){
            return value;
        }
        // 使用 与 运算把高为字节设置为0，符号位为0，该数字为正数
        return (int) value & 0xffff;
    }
    /**
     * int类型转换为long类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
     * @param value 有符号整型值
     * @return 无符号整型值
     */
    public static long toUnsignedIntValue(int value){
        if(!needWiden(value)){
            return value;
        }
        // 使用 与 运算把高为字节设置为0，符号位为0，该数字为正数
        // 注意后面那个L，如果没有这个L，java会认为这个数字为int类型,在运算过程中他会自动把他转换为long类型，那么最终的返回的值就还是负数，这是由于字节少的数字类型转换为字节多的类型时太会
        // 自动用符号位填充高位字节，，举个例子,
        // 例子1
        // int value = 100;
        // long lv = value; 此时低4个字节填充的是100，而高4个字节是0
        // 例子3
        // int value = 0xf0000000;
        // long lv = value; 此时低4个字节是0xf0000000， 而高4个字节是1，因为0xf0000000的符号位是1
        // 至于为什么是这样，这里不再解释，考虑一下计算机内部负数是用补码表示的这一原则
        return (long) value & 0xffffffffL;
    }
    /**
     * long类型转换为BigInteger类型，如果是value>0则直接返回，java语言自动支持转换，如果value<0则需要处理符号位：
     * @param value 有符号 long 值
     * @return 无符号 long 值
     */
    public static BigInteger toUnsignedLongValue(long value){
        if(!needWiden(value)){
            return BigInteger.valueOf(value);
        }
        // long型为8个字节，已经是占用字节最多的类型了，需要使用BigInteger来表示unsigned long了
        // 原理是获取long类型的字节数组，并把该数组作为BigInteger构造参数，然后构建BigInteger，
        // BigInteger所接收的byte数字是用大尾端表示的，因此在获取long的字节数组时使用ByteBuffer这个工具，
        // 关于大尾端、小尾端，请参见ByteOrder，或者一些操作系统相关的书籍
        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE/Byte.SIZE);
        buffer.order(ByteOrder.BIG_ENDIAN);
        buffer.putLong(value);
        return new BigInteger(1,buffer.array());
    }

}


















